﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Net;
using System.IO;
using System.Web;



//Отправляет запрос на торрент-трекер. - получает ответ на GET запрос.
//Обрабатывает ответы
using LTorrent.Torrent.BEncode;
using LTorrent.Torrent.File;
using LTorrent.Peers;

namespace LTorrent.Tracker
{
    class TrackerClient
    {
        //структура ответа, обработа пиров
        public struct Answer
        {
          public  string failureReason;
            public string warningMessage;
            public int interval;
            public int minInterval;
            public string trackerId;
            public int complete;//Сидеры
            public int incomplete; //Личеры
            public byte[] peers;
            public int downloaded;

            public string OptionalMessage;

            public Answer(string startMessage)
            {
                failureReason = null;
                warningMessage = null;
                interval = 0;
                minInterval = 0;
                trackerId = null;
                complete = 0;
                incomplete = 0;
                peers = null;
                downloaded = 0;

                OptionalMessage = startMessage;
            }

            public List<ExtendedPeer> SetPerrsArrayFromByteArray()
            {
                if (peers == null)
                    throw new ArgumentNullException("Не установлен список пиров");
                if (peers.Length % 6 != 0)
                    throw new ArgumentException("Неверный формат ответа от сервера. Длина пиров должна делиться на 6");

                List<ExtendedPeer> Result = new List<ExtendedPeer>();
                for (int i = 0; i < peers.Length; i += 6)
                    Result.Add(new ExtendedPeer(peers.Skip(i).Take(6).ToArray()));

                return Result;
            }

        };
        public Answer Response = new Answer("");
        /// <summary>
        /// Заполняет из ответа Stream структуру Response
        /// </summary>
        /// <param name="ResponseStream">Поток ответа</param>
        /// <returns>Метку успешности метода</returns>
        public bool parseResponse(Stream ResponseStream)
        {
            var BEncodeResponse = new FileBEncoding(ResponseStream);
            var X = BEncodeResponse.BenItems[0];
            if (X == null || X.DICItem == null)
                throw new Exception("Неверный ответ трекера.");
            try
            {
                for (int index = 0; index < X.DICItem.Count; index++)
                {//"сортировка" информации от сервера (список пиров получаем в идеале)
                    var Item = X.DICItem[index];
                    if (Item[0].ToString() == "failure reason")
                    {
                        Response.failureReason = Item[1].ToString();
                        return false;
                    }
                    if (Item[0].ToString() == "warning message")
                    {
                        Response.warningMessage = Item[1].ToString();
                        return false;
                    }
                    if (Item[0].ToString() == "interval")
                    {
                        bool Result = Int32.TryParse(Item[1].ToString(), out Response.interval);
                        if (!Result)
                            Response.OptionalMessage += "Ошибка перевода string в int переменной interval\n";
                    }
                    if (Item[0].ToString() == "min interval")
                    {
                        bool Result = Int32.TryParse(Item[1].ToString(), out Response.minInterval);
                        if (!Result)
                            Response.OptionalMessage += "Ошибка перевода string в int переменной min interval\n";
                    }
                    if (Item[0].ToString() == "tracker id")
                    {
                        Response.trackerId = Item[1].ToString();
                    }
                    if (Item[0].ToString() == "complete")
                    {
                        bool Result = Int32.TryParse(Item[1].ToString(), out Response.complete);
                        if (!Result)
                            Response.OptionalMessage += "Ошибка перевода string в int переменной complete\n";
                    }
                    if (Item[0].ToString() == "incomplete")
                    {
                        bool Result = Int32.TryParse(Item[1].ToString(), out Response.incomplete);
                        if (!Result)
                            Response.OptionalMessage += "Ошибка перевода string в int переменной incomplete\n";
                    }
                    if (Item[0].ToString() == "downloaded")
                    {
                        bool Result = Int32.TryParse(Item[1].ToString(), out Response.downloaded);
                        if (!Result)
                            Response.OptionalMessage += "Ошибка перевода string в int переменной downloaded\n";
                    }
                    if (Item[0].ToString() == "peers")
                    {
                        Response.peers = Item[1].STDItem.GETvalue;
                    }

                }
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
            return true;
        }

        public enum States
        {
            started = 0,
            empty = 1,
            stopped = 2,
            completed = 3
        };
      
        //Параметры для отправки на трекер
        protected string ClientID = null;
        public string peer_id
        {
        get{
              if (ClientID == null)
                {
                   string str = "mBLA01Torrent_Client";
                   ClientID = HttpUtility.UrlEncode(str);
                }
              return ClientID;
            }
        }

       protected string[] Event =  { "started", "", "stopped", "completed" };

        //временная часть, кроме порта. Нужно будет удалить!

 //      string info_hash = null;

       string _port = null;

       uint uploaded = 0, downloaded = 0;

       //int left;

       int compact;

        //методы
        public string Port
        {
            set
            {
                _port = value;
            }
        }
       

        public TrackerClient(int Port)
        {//установка порта
            this.Port = Port.ToString();
        }
        /// <summary>
        /// Проверка announce на корректность
        /// </summary>
        /// <param name="announce"></param>
        /// <returns></returns>
        private string checkAnnounce(string announce)
        {
            StringBuilder NewAnnounce = new StringBuilder(announce);
            int index = NewAnnounce.Length-1;
            while (index >= 0)
            {
                if (NewAnnounce[index] != '?') NewAnnounce.Remove(index, 1);
                else
                {
                    NewAnnounce.Remove(index, 1);
                    index = -1;
                }
                index--;
            }
            if (NewAnnounce.ToString() == "")
                return announce;
            return NewAnnounce.ToString();
        }


        /// <summary>
        /// создание ScrapeURL
        /// </summary>
        /// <param name="announce"></param>
        /// <returns></returns>
        private string GenerateScrapeURL(string announce)
        {
            StringBuilder NewStr = new StringBuilder(announce);
            if (announce.Contains("announce"))
            {
                NewStr.Replace("announce", "scrape");
                return NewStr.ToString();
            }
            return null;
         
        }
        /// <summary>
        /// Отправляет Scrape запрос 
        /// </summary>
        /// <param name="announce">URL</param>
        /// <param name="info_hash">Hash</param>
        /// <returns>Поток ответа</returns>
        public Stream sendScrapeRequest(string announce, byte[] info_hash)
        {//Scrape запрос. В случае невозможности возвращает null
            string Scrape = GenerateScrapeURL(announce);
            if (String.IsNullOrWhiteSpace(Scrape))
                return null;
            string RequestStr = string.Format("{0}?info_hash={1}", Scrape,HttpUtility.UrlEncode(info_hash));
            var GetReq = HttpWebRequest.Create(new Uri(RequestStr, UriKind.Absolute)) as HttpWebRequest;
            GetReq.Method = "GET";
            var GetResp = GetReq.GetResponse();
            return GetResp.GetResponseStream();
        }
        /// <summary>
        /// Отправляет Announce запрос на получение пиров
        /// </summary>
        /// <param name="announce">URL</param>
        /// <param name="info_hash">Hash</param>
        /// <param name="left">Осталось скачать</param>
        /// <param name="eventState">"положение" торрента</param>
        /// <returns>Поток ответа</returns>
        public Stream sendRequestCompact(string announce,byte[] info_hash, UInt64 left, States eventState)
        {
           
            if (string.IsNullOrEmpty(_port))//TODO - в BEncode определить Exceptions!!!
                throw new Exception("Не определен порт!");
            
            //За раз зададим строку (вот оно - преимущество широкоформатных экранов)
            string RequestStr = string.Format("{0}?info_hash={1}&peer_id={2}&port={3}&uploaded={4}&downloaded={5}&left={6}&compact=1&event={7}",
                checkAnnounce(announce),HttpUtility.UrlEncode(info_hash), peer_id, _port, uploaded.ToString(), downloaded.ToString(), left.ToString(), Event[(int)eventState]);
            
          //  RequestStr = System.Web.HttpUtility.UrlEncode(RequestStr);
            var GetReq = HttpWebRequest.Create(new Uri(RequestStr, UriKind.Absolute));
            GetReq.Method = "GET";
            var GetResp = GetReq.GetResponse();

            return GetResp.GetResponseStream();
        }

       /*public string sendRequest(string announce, byte[] info_hash, uint left, States eventState)
        {//Не нужен.... Обычная практика - компактные ответы
            if (string.IsNullOrEmpty(port))//TODO - в BEncode определить Exceptions!!!
                throw new Exception("Не определен порт!");

            //За раз зададим строку (вот оно - преимущество широкоформатных экранов)
            string RequestStr = string.Format("{0}?info_hash={1}&peer_id={2}&port={3}&uploaded={4}&downloaded={5}&left={6}&event={7}",
                    checkAnnounce(announce), HttpUtility.UrlEncode(info_hash), peer_id, port, uploaded.ToString(), downloaded.ToString(), left.ToString(), Event[(int)eventState]);

            //  RequestStr = System.Web.HttpUtility.UrlEncode(RequestStr);
            var GetReq = HttpWebRequest.Create(new Uri(RequestStr, UriKind.Absolute)) as HttpWebRequest;
            GetReq.Method = "GET";
            var GetResp = GetReq.GetResponse();
            Stream GetStream = GetResp.GetResponseStream();
            StreamReader GetReader = new StreamReader(GetStream);
            return GetReader.ReadToEnd(); 
        }*/

    }
}
